package cn.blossoming.service.base.utils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.SSLInitializationException;
import org.springframework.util.Assert;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

/**
 * @author zyb
 * @since 2017-05-19 上午11:54
 */
@Slf4j
public class HttpClientFactory {
    @Data
    public static class Config {
        private int maxTotal = 300;
        private int maxPerRoute = 100;
        private int requestConfigConnectRequestTime = 1000;
        private int requestConfigConnectTimeout = 1000;
        private int requestConfigSocketTimeout = 1000 * 3;
        private boolean requestConfigRedirectsEnabled = false;

    }

    public static HttpClient createHttpClient() {
        Config config = new Config();
        return createHttpClient(config);
    }

    public static HttpClient createHttpClient(Config config) {
        return createHttpClient(config, false);
    }

    public static HttpClient createSslHttpClient(Config config) {

        return createHttpClient(config, true);
    }

    private static HttpClient createHttpClient(Config config, boolean isSsLHttpClient) {
        Assert.notNull(config, "httpClientFactory config must be not null!");
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(config.getMaxTotal());
        cm.setDefaultMaxPerRoute(config.getMaxPerRoute());

        new IdleConnectionMonitorThread(cm).start();

        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(config.getRequestConfigConnectRequestTime())
            .setConnectTimeout(config.getRequestConfigConnectTimeout())
            .setSocketTimeout(config.getRequestConfigSocketTimeout())
            .setRedirectsEnabled(config.requestConfigRedirectsEnabled)
            .build();
        if (isSsLHttpClient) {
            try {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                TrustStrategy trustStrategy = (x509Certificates, s) -> true;
                SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(keyStore, trustStrategy).build();
                SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
                return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
            } catch (SSLInitializationException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
                throw new RuntimeException("sslHttpClient init exception.", e);
            }
        } else {

            return HttpClientBuilder.create()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(requestConfig)
                .build();
        }

    }

    public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super("http-client-monitor-thread");
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(5000);
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        connMgr.closeIdleConnections(3, TimeUnit.MINUTES);
                    }
                }
            } catch (InterruptedException ex) {
                shutdown();
            }
        }

        void shutdown() {
            shutdown = true;
            synchronized (this) {
                notifyAll();
            }
        }

    }

}
